/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package byzantine;

import byzantine.views.View;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author rpathan
 */
public class ByzantineModel {

    public static final String NO_TARGET_SELECTED = "No Target Selected";
    public static final String COMMANDER = "Loyal Commander";
    public static final String COMMANDER_REBEL = "Rebel Commander";
    public static final String GENERAL = "Loyal General";
    public static final String GENERAL_REBEL = "Rebel General";
    public static final String UNDETERMINED_VALUE = "?";
    public static final String TRAITOR_VALUE = "X";
    public static final String ATTACK_VALUE = "A";
    public static final String RETREAT_VALUE = "R";
    public static final String GRAPH_CURRENT_COLOR = "red";
    public static final String GRAPH_PREVIOUS_COLOR = "green";
    public static final String GRAPH_PATH_COLOR = "green";
    public static final int MAX_GENERALS = 8;
    public static final int MIN_GENERALS_TO_RUN = 2;
    private List<View> observers;
    private ArrayList<General> generals;
    private ArrayList<Integer> generalIndices;
    private LamportAlgorithm algorithmInstance;
    private Integer numSteps;
    private General selectedGeneral;
    private Integer examinedStep;
    private boolean attackCommand;
    private boolean selectedGeneralChanged;
    private boolean simulationStatusChanged;
    private boolean commanderStatusChanged;
    private boolean numGeneralsChanged;
    private boolean simulationRunning;
    private boolean hasCommander;
    private boolean examinedStepChanged;
    private boolean attackCommandChanged;

    public ByzantineModel() {
        observers = new LinkedList<View>();
        generals = new ArrayList<General>();

        generalIndices = new ArrayList<Integer>();
        for (int i = 0; i < MAX_GENERALS; i++) {
            generalIndices.add(new Integer(i));
        }

        examinedStep = 0;
        numSteps = 0;

        selectedGeneral = null;
        selectedGeneralChanged = false;

        simulationStatusChanged = false;
        commanderStatusChanged = false;
        numGeneralsChanged = false;
        examinedStepChanged = false;
        attackCommandChanged = false;
        attackCommand = true;

        simulationRunning = false;
        hasCommander = false;
    }

    public void addGeneral(String type) {

        if (generalIndices.size() > 0) {
            if (type.equals(COMMANDER) || type.equals(COMMANDER_REBEL)) {
                hasCommander = true;
                commanderStatusChanged = true;
            }
            General g = new General(generalIndices.remove(0), type);
            generals.add(g);
            numGeneralsChanged = true;
            notifyObservers();
        } else {
            System.err.println("Error: No general slots remaining");
        }
    }

    public void removeGeneral(General g) {

        if (generalIndices.size() < MAX_GENERALS) {
            int ID = g.getID();
            String type = g.getType();
            if (type.equals(COMMANDER) || type.equals(COMMANDER_REBEL)) {
                hasCommander = false;
                commanderStatusChanged = true;
            }
            generalIndices.add(ID);
            numGeneralsChanged = true;
            generals.remove(g);
            notifyObservers();
        }
    }

    public Integer getNumGenerals() {
        return generals.size();
    }

    public Integer getNumLoyal() {
        int loyalCount = 0;
        for (General general : generals) {
            if (general.getType().toString().equals(ByzantineModel.COMMANDER)
                    || general.getType().toString().equals(ByzantineModel.GENERAL)) {
                loyalCount++;
            }
        }
        return loyalCount;
    }

    public Integer getNumTraitors() {
        int traitorCount = 0;
        for (General general : generals) {
            if (general.getType().toString().equals(ByzantineModel.COMMANDER_REBEL)
                    || general.getType().toString().equals(ByzantineModel.GENERAL_REBEL)) {
                traitorCount++;
            }
        }
        return traitorCount;
    }

    public Integer getNumRounds() {
        if (algorithmInstance != null) {
            return algorithmInstance.getNumRounds();
        } else {
            return 0;
        }
    }

    public Integer getNumSteps() {
        return numSteps;
    }

    public Integer getNumMessages() {
        if (algorithmInstance != null) {
            return algorithmInstance.getNumMessages();
        } else {
            return 0;
        }
    }

    public Long getSimulationTime() {
        if (algorithmInstance != null) {
            return algorithmInstance.getSimulationTime();
        } else {
            return 0l;
        }
    }

    public General getGeneral(int index) {
        return generals.get(index);
    }

    public General getGeneralByID(Integer ID) {
        for (General general : generals) {
            if (general.getID().equals(ID)) {
                return general;
            }
        }
        return null;
    }

    public General getSelectedGeneral() {
        return selectedGeneral;
    }

    public void setSelectedGeneral(General g) {
        if (g == selectedGeneral) {
            return;
        }

        selectedGeneral = g;
        selectedGeneralChanged = true;

        generateGraphFile();

        notifyObservers();
    }

    public Integer getExaminedStep() {
        return examinedStep;
    }

    public void setExaminedStep(Integer examinedStep) {
        if (examinedStep >= 0 && examinedStep < numSteps) {
            this.examinedStep = examinedStep;
            examinedStepChanged = true;
            generateGraphFile();
            notifyObservers();
        } else {
            System.err.println("Error: Examined step out of bounds");
        }
    }

    public boolean hasSelectedGeneralChanged() {
        return selectedGeneralChanged;
    }

    public boolean hasNumGeneralsChanged() {
        return numGeneralsChanged;
    }

    public boolean hasSimulationStatusChanged() {
        return simulationStatusChanged;
    }

    public boolean hasCommanderStatusChanged() {
        return commanderStatusChanged;
    }

    public boolean hasExaminedStepChanged() {
        return examinedStepChanged;
    }

    public boolean hasAttackCommandChanged() {
        return attackCommandChanged;
    }

    public boolean isSimulationRunning() {
        return simulationRunning;
    }

    public boolean hasCommander() {
        return hasCommander;
    }

    public boolean getAttackCommand() {
        return attackCommand;
    }

    public void setAttackCommand(boolean command) {
        attackCommand = command;
        attackCommandChanged = true;
    }

    public void runSimulation() {
        algorithmInstance = new LamportAlgorithm(generals, attackCommand);
        algorithmInstance.run();
        for (int i = 0; i < generals.size(); i++) {
            if (i == algorithmInstance.getCommanderIndex()) {
                continue;
            }
        }
        numSteps = algorithmInstance.getNumRounds() * 2;
        
        if (selectedGeneral != null
                && selectedGeneral.getType().equals(ByzantineModel.COMMANDER) == false
                && selectedGeneral.getType().equals(ByzantineModel.COMMANDER_REBEL) == false) {
            generateGraphFile();
        }

        simulationRunning = true;
        simulationStatusChanged = true;
        notifyObservers();
    }

    public void resetSimulation() {
        generals.clear();
        generalIndices.clear();
        for (int i = 0; i < MAX_GENERALS; i++) {
            generalIndices.add(new Integer(i));
        }

        examinedStep = 0;
        examinedStepChanged = true;
        algorithmInstance = null;

        selectedGeneral = null;
        selectedGeneralChanged = true;

        numGeneralsChanged = true;
        simulationStatusChanged = true;
        commanderStatusChanged = true;
        attackCommandChanged = true;
        attackCommand = true;

        simulationRunning = false;
        hasCommander = false;
        notifyObservers();
    }

    public Integer getNumAttacking() {
        if (algorithmInstance != null) {
            return algorithmInstance.getNumAttacking();
        } else {
            return 0;
        }
    }

    public Integer getNumRetreating() {
        if (algorithmInstance != null) {
            return algorithmInstance.getNumRetreating();
        } else {
            return 0;
        }
    }

    public boolean getAlgorithmOutcome() {
        if (algorithmInstance != null) {
            return algorithmInstance.getAlgorithmOutcome();
        } else {
            return false;
        }
    }

    public void generateGraphFile() {

        try {

            BufferedWriter bw = new BufferedWriter(new FileWriter("unformatted_graph.dot"));
            bw.write("graph byzantine {\n");
            bw.write("rankdir = LR;\n");
            bw.write("nodesep = 0.2;\n");
            if (examinedStep < algorithmInstance.getNumRounds()) {
                bw.write("label = \"Received Messages for:  " + selectedGeneral.getID().toString() + " - " + selectedGeneral.getType() + "\"\n");
            } else {
                bw.write("label = \"Decision Messages for:  " + selectedGeneral.getID().toString() + " - " + selectedGeneral.getType() + "\"\n");     
            }
            bw.write("labelloc = t;\n");
            bw.write("node [fontsize = 10, width = 0.005, height = 0.005, shape = ellipse];\n");
            bw.write("edge [fontsize = 12, arrowsize = 0.25];\n");

            int loopSteps = examinedStep;
            if (examinedStep >= algorithmInstance.getNumRounds()) {
                loopSteps = algorithmInstance.getNumRounds() - 1;
            }

            for (int i = 0; i <= loopSteps; i++) {
                ArrayList<Message> receivedMessages;
                if (examinedStep + i >= numSteps - 1) {
                    receivedMessages = selectedGeneral.getDecisionMessages(i);
                } else {
                    receivedMessages = selectedGeneral.getReceivedMessages(i);
                }
                if (i == 0) {
                    if (examinedStep == 0 || examinedStep == numSteps - 1) {
                        bw.write("\"" + receivedMessages.get(0).toString() + "\" [color=\"" + GRAPH_CURRENT_COLOR + "\"]\n");
                    } else if (examinedStep == 1) {
                        bw.write("\"" + receivedMessages.get(0).toString() + "\" [color=\"" + GRAPH_PREVIOUS_COLOR + "\"]\n");
                    } else {
                        bw.write("\"" + receivedMessages.get(0).toString() + "\"\n");
                    }

                } else {
                    ArrayList<Message> previousMessages;
                    if (examinedStep + i >= numSteps) {
                        previousMessages = selectedGeneral.getDecisionMessages(i - 1);
                    } else {
                        previousMessages = selectedGeneral.getReceivedMessages(i - 1);

                    }
                    for (int j = 0; j < previousMessages.size(); j++) {
                        if (i == examinedStep) {
                            bw.write("\"" + previousMessages.get(j).toString() + "\" [color=\"" + GRAPH_PREVIOUS_COLOR + "\"]\n");
                        }

                        for (int k = j * (generals.size() - i); k < (j + 1) * (generals.size() - i); k++) {
                            if (i == examinedStep || i == numSteps - examinedStep - 1) {
                                bw.write("\"" + receivedMessages.get(k).toString() + "\" [color=\"" + GRAPH_CURRENT_COLOR + "\"]\n");
                            } else if (i == numSteps - examinedStep) {
                                bw.write("\"" + receivedMessages.get(k).toString() + "\" [color=\"" + GRAPH_PREVIOUS_COLOR + "\"]\n");
                            }
                            if (i == examinedStep || i == numSteps - examinedStep) {
                                bw.write("\"" + previousMessages.get(j).toString() + "\"--\"" + receivedMessages.get(k).toString() + "\" [color=\"" + GRAPH_PATH_COLOR + "\"]\n");
                            } else {
                                bw.write("\"" + previousMessages.get(j).toString() + "\"--\"" + receivedMessages.get(k).toString() + "\"\n");
                            }
                            
                        }
                    }
                }
            }
            bw.write("}\n");
            bw.close();
        } catch (Exception e) {
            //e.printStackTrace(System.err);
        }
    }

    public void addObserver(View view) {
        observers.add(view);
    }

    private void notifyObservers() {
        for (View observer : observers) {
            observer.valueChanged();
        }
        selectedGeneralChanged = false;
        numGeneralsChanged = false;
        simulationStatusChanged = false;
        commanderStatusChanged = false;
        examinedStepChanged = false;
        attackCommandChanged = false;
    }
}
